<template>
  <div id="container">
  </div>
</template>

<script setup>
import * as THREE from 'three'
import { onMounted } from 'vue';
// 引入轨道控制器扩展库OrbitControls.js
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
//引入性能监视器stats.js
import Stats from 'three/addons/libs/stats.module.js';
// 引入dat.gui.js的一个类GUI
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
// 引入gltf模型加载库GLTFLoader.js
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
// 通过添加一个Capsule对象来实现碰撞检测。
// Capsule - 胶囊体
import { Capsule } from 'three/examples/jsm/math/Capsule';
// 为什么要使用Capsule对象呢？ 因为，在Octree中提供了Capsule对象碰撞的方法，让我们可以直接使用来更容易的实现碰撞检测。

import { Octree } from 'three/examples/jsm/math/Octree';
// PointerLockControls控件，通过该控件可以比较方便实现第一人称的一个操作方式
import { PointerLockControls } from 'three/examples/jsm/controls/PointerLockControls';


onMounted(() => {


  const clock = new THREE.Clock();

  const scene = new THREE.Scene();
  scene.background = new THREE.Color(0x88ccee);
  scene.fog = new THREE.Fog(0x88ccee, 0, 50);

  const camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 10000);
  camera.rotation.order = 'YXZ';
  camera.position.set(0, 0, 0);

  const fillLight1 = new THREE.HemisphereLight(0x4488bb, 0x002244, 2.5);
  fillLight1.position.set(2, 1, 1);
  // scene.add(fillLight1);

  const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
  directionalLight.position.set(-5, 25, -1);
  directionalLight.castShadow = true;
  directionalLight.shadow.camera.near = 0.01;
  directionalLight.shadow.camera.far = 500;
  directionalLight.shadow.camera.right = 30;
  directionalLight.shadow.camera.left = -30;
  directionalLight.shadow.camera.top = 30;
  directionalLight.shadow.camera.bottom = -30;
  directionalLight.shadow.mapSize.width = 1024;
  directionalLight.shadow.mapSize.height = 1024;
  directionalLight.shadow.radius = 4;
  directionalLight.shadow.bias = -0.00006;
  scene.add(directionalLight);


  const container = document.getElementById('container');
  console.log('container', container)
  const renderer = new THREE.WebGLRenderer({
    antialias: true
  });
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(window.innerWidth, window.innerHeight);
  renderer.shadowMap.enabled = true;
  renderer.shadowMap.type = THREE.VSMShadowMap;
  renderer.outputEncoding = THREE.sRGBEncoding;
  renderer.toneMapping = THREE.ACESFilmicToneMapping;
  container.appendChild(renderer.domElement);

  let oControls = new OrbitControls(camera, container);
  oControls.update();

  const stats = new Stats();
  stats.domElement.style.position = 'absolute';
  stats.domElement.style.top = '0px';
  container.appendChild(stats.domElement);




  scene.add(new THREE.AmbientLight(0xe0ffff, 1))

  // scene.add(new THREE.HemisphereLight("#ffffff", "#6b6b6b", 2));

  const geometry = new THREE.CapsuleGeometry(0.35, 0.7, 4, 8);
  const material = new THREE.MeshBasicMaterial({
    color: 0x00ff00,
    wireframe: true
  });
  const capsule = new THREE.Mesh(geometry, material);
  scene.add(capsule);

  const GRAVITY = 30;

  const NUM_SPHERES = 100;
  const SPHERE_RADIUS = 0.2;

  const STEPS_PER_FRAME = 5;

  const sphereGeometry = new THREE.IcosahedronGeometry(SPHERE_RADIUS, 5);
  const sphereMaterial = new THREE.MeshLambertMaterial({
    color: 0xbbbb44
  });

  let mesh2, activeAction, peopleAnimations, tween, mixer, peopleBox;
  let mixerArr = [];
  const spheres = [];
  let sphereIdx = 0;

  const worldOctree = new Octree();

  const playerCollider = new Capsule(new THREE.Vector3(0, 0.35, 0), new THREE.Vector3(0, 1, 0), 0.26);

  const playerVelocity = new THREE.Vector3();
  const playerDirection = new THREE.Vector3();

  let playerOnFloor = false;
  let mouseTime = 0;

  const keyStates = {};

  const vector1 = new THREE.Vector3();
  const vector2 = new THREE.Vector3();
  const vector3 = new THREE.Vector3();

  document.addEventListener('keydown', (event) => {

    keyStates[event.code] = true;

  });

  document.addEventListener('keyup', (event) => {

    keyStates[event.code] = false;

  });

  window.addEventListener('resize', onWindowResize);

  function onWindowResize() {

    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();

    renderer.setSize(window.innerWidth, window.innerHeight);

  }

  function playerCollisions() {

    const result = worldOctree.capsuleIntersect(playerCollider);

    playerOnFloor = false;

    if (result) {

      playerOnFloor = result.normal.y > 0;

      if (!playerOnFloor) {

        playerVelocity.addScaledVector(result.normal, -result.normal.dot(playerVelocity));

      }

      playerCollider.translate(result.normal.multiplyScalar(result.depth));

    }

  }

  function updatePlayer(deltaTime) {

    let damping = Math.exp(-4 * deltaTime) - 1;

    if (!playerOnFloor) {

      playerVelocity.y -= GRAVITY * deltaTime;

      // small air resistance
      damping *= 0.1;

    }

    playerVelocity.addScaledVector(playerVelocity, damping);

    const deltaPosition = playerVelocity.clone().multiplyScalar(deltaTime);
    playerCollider.translate(deltaPosition);

    playerCollisions();

    let resPos = playerCollider.end;
    capsule.position.copy(resPos);

    if (mesh2) {
      mesh2.position.copy(resPos.clone().setY(resPos.y - 0.7));

      let pos = mesh2.position.clone();
      pos = pos.setY(pos.y + 0.7)
      oControls.target = pos;
      oControls.update();
    } else {
      oControls.object.position.set(0, 1, -2)
      oControls.update(true); // 初始化
    }

  }

  function getForwardVector() {

    capsule.getWorldDirection(playerDirection);
    playerDirection.y = 0;
    playerDirection.normalize();

    return playerDirection;

  }

  function getSideVector() {

    capsule.getWorldDirection(playerDirection);
    playerDirection.y = 0;
    playerDirection.normalize();
    playerDirection.cross(capsule.up);

    return playerDirection;

  }

  function controls(deltaTime) {
    // console.log(camera.rotation)
    capsule.rotation.y = camera.rotation.y;

    // gives a bit of air control
    const speedDelta = deltaTime * (playerOnFloor ? 25 : 8);

    if (keyStates['KeyW']) {
      playerVelocity.add(getForwardVector().multiplyScalar(-speedDelta));
    }

    if (keyStates['KeyS']) {
      playerVelocity.add(getForwardVector().multiplyScalar(speedDelta));
    }

    if (keyStates['KeyA']) {
      playerVelocity.add(getSideVector().multiplyScalar(speedDelta));
    }

    if (keyStates['KeyD']) {
      playerVelocity.add(getSideVector().multiplyScalar(-speedDelta));
    }

    if (playerOnFloor) {
      if (keyStates['Space']) {
        playerVelocity.y = 8;

      }
    }
  }

  // 创建文字精灵
  var getTextCanvas = function (text) {
    let option = {
      fontFamily: 'Arial',
      fontSize: 30,
      fontWeight: 'bold',
      color: '#ffffff',
      actualFontSize: 0.08,
    },
      canvas, context, textWidth, texture, materialObj, spriteObj;
    canvas = document.createElement('canvas');
    context = canvas.getContext('2d');
    // 先设置字体大小后获取文本宽度
    context.font = option.fontWeight + ' ' + option.fontSize + 'px ' + option.fontFamily;
    textWidth = context.measureText(text).width;

    canvas.width = textWidth;
    canvas.height = option.fontSize;

    context.textAlign = "center";
    context.textBaseline = "middle";
    context.fillStyle = option.color;
    context.font = option.fontWeight + ' ' + option.fontSize + 'px ' + option.fontFamily;
    context.fillText(text, textWidth / 2, option.fontSize / 1.8);

    texture = new THREE.CanvasTexture(canvas);
    materialObj = new THREE.SpriteMaterial({
      map: texture
    });
    spriteObj = new THREE.Sprite(materialObj);
    spriteObj.scale.set(textWidth / option.fontSize * option.actualFontSize, option.actualFontSize, option
      .actualFontSize);

    return spriteObj;
  }

  const loader = new GLTFLoader().setPath('assets/models/');

  //CircleGeometry的顶点UV坐标是按照圆形采样纹理贴图
  const geometry1 = new THREE.PlaneGeometry(2000, 2000);
  // //纹理贴图加载器TextureLoader
  // .load()方法加载图像，返回一个纹理对象Texture
  let texture = new THREE.TextureLoader().load(`static/floor1.jpg`)

  // 设置阵列模式
  texture.wrapS = THREE.RepeatWrapping;
  texture.wrapT = THREE.RepeatWrapping;
  // uv两个方向纹理重复数量
  texture.repeat.set(12, 12);//注意选择合适的阵列数量
  // 添加一个辅助网格地面
  const gridHelper = new THREE.GridHelper(3000, 25, 0x004444, 0x004444);
  scene.add(gridHelper)
  const material1 = new THREE.MeshBasicMaterial({
    //   \颜色贴图和color属性颜色值会混合

    // 材质的颜色贴图属性.map设置后，模型会从纹理贴图上采集像素值，这时候一般来说不需要再设置材质颜色.color。.map贴图之所以称之为颜色贴图就是因为网格模型会获得颜色贴图的颜色值RGB。

    // 颜色贴图map和color属性颜色值会混合。如果没有特殊需要，设置了颜色贴图.map, 不用设置color的值，color默认白色0xffffff。
    // color: 0x0000ff, //材质颜色
    // wireframe: true //线条模式渲染mesh对应的三角形数据
    // side: THREE.FrontSide, //默认只有正面可见
    // 设置纹理贴图：Texture对象作为材质map属性的属性值
    side: THREE.DoubleSide,
    map: texture //map表示材质的颜色贴图属性
  });
  const mesh = new THREE.Mesh(geometry1, material1); //网格模型对象Mesh
  mesh.rotation.x = -Math.PI / 2;
  scene.add(mesh);

  worldOctree.fromGraphNode(mesh); // 通过Octree对象来构建节点

  animate();


  const geometry2 = new THREE.SphereGeometry(50);
  const material2 = new THREE.MeshBasicMaterial({
    color: 0x0000ff, //材质颜色
  });
  mesh2 = new THREE.Mesh(geometry2, material2); //网格模型对象Mesh

  mesh2.scale.set(0.7, 0.7, 0.7);
  peopleAnimations = mesh2.animations;

  // 主人物名字
  let spriteText = getTextCanvas("左本Web3D");
  spriteText.position.set(0, 1.95, 0);
  mesh2.add(spriteText);

  const bbox = new THREE.Box3().setFromObject(mesh2);

  // 获取包围盒的中心点
  const center = new THREE.Vector3();
  bbox.getCenter(center);

  // 将物体移动到中心点
  mesh2.position.sub(center);

  // 组合对象添加到场景中
  scene.add(mesh2);

  mixer = new THREE.AnimationMixer(mesh2);
  mixerArr.push(mixer)
  activeAction = mixer.clipAction(peopleAnimations[1]);
  activeAction.play();


  function teleportPlayerIfOob() {

    if (camera.position.y <= -25) {

      playerCollider.start.set(0, 0.35, 0);
      playerCollider.end.set(0, 1, 0);
      playerCollider.radius = 0.35;
      camera.position.copy(playerCollider.end);
      camera.rotation.set(0, 0, 0);

    }

  }


  function animate() {

    const deltaTime = Math.min(0.05, clock.getDelta()) / STEPS_PER_FRAME;

    // we look for collisions in substeps to mitigate the risk of
    // an object traversing another too quickly for detection.

    for (let i = 0; i < mixerArr.length; i++) {
      mixerArr[i].update(clock.getDelta());
    }

    for (let i = 0; i < STEPS_PER_FRAME; i++) {

      controls(deltaTime);

      updatePlayer(deltaTime);

      // updateSpheres(deltaTime);

      // teleportPlayerIfOob();

    }

    renderer.render(scene, camera);

    stats.update();

    requestAnimationFrame(animate);
  }

})
</script>
<style scoped>
#container {
  width: 500px;
  height: 500px;
  margin: 100px auto;
  background: gray;
}
</style>